package com.example.demo.test1.service.impl;

import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.exception.BaseServiceException;
import com.example.demo.test1.entity.Account;
import com.example.demo.test1.entity.AccountViewDTO;
import com.example.demo.test1.entity.Type;
import com.example.demo.test1.mapper.AccountMapper;
import com.example.demo.test1.mapper.TypeMapper;
import com.example.demo.test1.service.AccountService;
import com.example.demo.utils.*;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.util.StringUtil;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.InputStreamSource;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring5.SpringTemplateEngine;

import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * @author cys1731 2020/4/20 17:26
 */
@SuppressWarnings("AlibabaAvoidDoubleOrFloatEqualCompare")
@Slf4j
@Service
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService {
    @Autowired
    private AccountMapper accountMapper;
    private SpringTemplateEngine templateEngine;
    private static LinkedHashMap<String, String> localResourceTypes;
    @Autowired
    private TypeMapper typeMapper;
//    private final AccountProperties configProperties;
//
//    @Autowired
//    public AccountServiceImpl(AccountMapper accountMapper, SpringTemplateEngine templateEngine, AccountProperties configProperties) {
//        this.accountMapper = accountMapper;
//        this.templateEngine = templateEngine;
//        this.configProperties = configProperties;
//    }

    @Override
    public ArrayList<String> listTypeName() {
        return accountMapper.listTypeName();
    }


    /**
     * 检查是否有账单数据
     */
    @Override
    public boolean exportCheckAccountExcelByYear(int year, String accountGroupId) {
        List<Account> accounts;
        QueryWrapper<Account> wrapper = new QueryWrapper<>();
        if ("0".equals(accountGroupId)){
            wrapper.eq("year", year).orderByAsc("month");
        } else {
            wrapper.eq("year", year).eq("account_group_id", accountGroupId).orderByAsc("month");
        }
        accounts = this.list(wrapper.last("limit 1"));
        return accounts != null && accounts.size() > 0;
    }

    /**
     * 导出Excel：按年导出当前登录用户可见所有资源的账单
     */
    @Override
    public void exportAccountExcelByYear(int year, String accountGroupId, HttpServletResponse response) {

        List<Account> accounts;
        QueryWrapper<Account> wrapper = new QueryWrapper<>();
        if ("0".equals(accountGroupId)){
            wrapper.eq("year", year).orderByAsc("month");
        } else {
            wrapper.eq("year", year).eq("account_group_id", accountGroupId).orderByAsc("month");
        }
        accounts = this.list(wrapper);

        LinkedHashMap<String, List<Account>> accountByType = this.accountByType(accounts).getValue();
        SXSSFWorkbook workbook = this.accountExcel(accountByType);
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        try {
            response.setHeader("content-disposition", "attachment;filename="
                    + URLEncoder.encode(year + "详细账单数据-" + "谢正华" + Constant.SUFFIX_EXCEL, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            log.error("系统异常[导出年账单失败][" + e.getMessage() + "]");
            throw new BaseServiceException("系统异常[导出年账单失败][" + e.getMessage() + "]");
        }
        response.setHeader("Access-Control-Expose-Headers", "content-disposition");
        try (BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream())) {
            workbook.write(bos);
            bos.flush();
            workbook.dispose();
        } catch (Exception e) {
            log.error("系统异常[导出年账单失败][" + e.getMessage() + "]");
            throw new BaseServiceException("系统异常[导出年账单失败][" + e.getMessage() + "]");
        }
    }

    /**
     * 导出Excel文件
     *
     * @param accountsMap {@code <type英文, List<Account>>, <类型, List<Account>>}
     * @return XSSFWorkbook
     */
    private SXSSFWorkbook accountExcel(@NonNull LinkedHashMap<String, List<Account>> accountsMap) {
        LinkedHashMap<String, String> types = getLocalResourceTypes();
        SXSSFWorkbook workbook = new SXSSFWorkbook();
        for (Map.Entry<String, List<Account>> entry : accountsMap.entrySet()) {
            String typeValue = types.get(entry.getKey());
            Sheet sheet = workbook.createSheet(typeValue);
            sheet.setDefaultRowHeight((short) 480);
            // 表头
            Row row0 = sheet.createRow(0);
            row0.createCell(0).setCellValue("年");
            sheet.setColumnWidth(0, 6 * 256);
            row0.createCell(1).setCellValue("月");
            sheet.setColumnWidth(1, 4 * 256);
            row0.createCell(2).setCellValue("服务类型");
            sheet.setColumnWidth(2, 15 * 256);
            row0.createCell(3).setCellValue("服务名称");
            sheet.setColumnWidth(3, 25 * 256);
            row0.createCell(4).setCellValue("服务规格");
            sheet.setColumnWidth(4, 20 * 256);
            row0.createCell(5).setCellValue("规格单价");
            sheet.setColumnWidth(5, 10 * 256);
            row0.createCell(6).setCellValue("费用");
            sheet.setColumnWidth(6, 10 * 256);
            row0.createCell(7).setCellValue("责任人");
            sheet.setColumnWidth(7, 15 * 256);
            row0.createCell(8).setCellValue("服务开通时间");
            sheet.setColumnWidth(8, 22 * 256);
            row0.createCell(9).setCellValue("服务删除时间");
            sheet.setColumnWidth(9, 22 * 256);
            row0.createCell(10).setCellValue("关联订单名称");
            sheet.setColumnWidth(10, 20 * 256);
            row0.createCell(11).setCellValue("关联系统/项目");
            sheet.setColumnWidth(11, 20 * 256);
            row0.createCell(12).setCellValue("费用承担组织");
            sheet.setColumnWidth(12, 20 * 256);
            row0.createCell(13).setCellValue("实际完整组织");
            sheet.setColumnWidth(13, 20 * 256);
            // 水平居中 垂直居中 宋体 加粗
            CellStyle cellStyle0 = workbook.createCellStyle();
            cellStyle0.setAlignment(XSSFCellStyle.ALIGN_CENTER);
            cellStyle0.setVerticalAlignment(XSSFCellStyle.VERTICAL_CENTER);
            Font font0 = workbook.createFont();
            font0.setFontName("宋体");
            font0.setBoldweight(Font.BOLDWEIGHT_BOLD);
            cellStyle0.setFont(font0);
            for (int i = 0; i < 14; i++) {
                row0.getCell(i).setCellStyle(cellStyle0);
            }
            row0.setRowStyle(cellStyle0);
            // 表数据
            List<Account> accounts = entry.getValue();
            if (accounts == null || accounts.size() == 0) {
                continue;
            }
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            // 左居中 垂直居中 宋体
            CellStyle cellStyle1 = workbook.createCellStyle();
            cellStyle1.setAlignment(XSSFCellStyle.ALIGN_LEFT);
            cellStyle1.setVerticalAlignment(XSSFCellStyle.VERTICAL_CENTER);
            Font font = workbook.createFont();
            font.setFontName("宋体");
            cellStyle1.setFont(font);
            // 右居中 垂直居中 宋体
            CellStyle cellStyle2 = workbook.createCellStyle();
            cellStyle2.setAlignment(XSSFCellStyle.ALIGN_RIGHT);
            cellStyle2.setVerticalAlignment(XSSFCellStyle.VERTICAL_CENTER);
            cellStyle2.setFont(font);
            // 没有数据的用"——"表示
            CollectionUtil.foreach(accounts, (account, idx) -> {
                Row row = sheet.createRow(idx + 1);
                row.createCell(0).setCellValue(account.getYear());
                row.createCell(1).setCellValue(account.getMonth());
                row.createCell(2).setCellValue(typeValue);
                row.createCell(3).setCellValue(account.getResourceName());
                String norm = account.getNorm();
                if (StringUtils.isEmpty(norm)){
                    norm = "——";
                } else {
                    if ("Baremetal".equals(account.getResourceType())){
                        String[] strings = norm.split("\\*");
                        String memory = strings[1].substring(0, strings[1].length()-2);
                        String memoryByGb = Integer.parseInt(memory) / 1024 + "GB";
                        norm = strings[0] + "*" + memoryByGb + "*" + strings[2];
                    }
                }
                row.createCell(4).setCellValue(norm);
                Float price = account.getNormPrice();
                if (price == null) {
                    row.createCell(5).setCellValue("——");
                } else {
                    row.createCell(5).setCellValue(NumberUtil.floatToDoubleScale(price));
                }
                row.createCell(6).setCellValue(NumberUtil.floatToDoubleScale(account.getCost()));
                row.createCell(7).setCellValue(account.getUserName());
                LocalDateTime startTime = account.getStartTime();
                String startTimeStr = startTime == null ? "——" : formatter.format(startTime);
                row.createCell(8).setCellValue(startTimeStr);
                LocalDateTime deleteTime = account.getDeleteTime();
                String deleteTimeStr = deleteTime == null ? "——" : formatter.format(deleteTime);
                row.createCell(9).setCellValue(deleteTimeStr);
                String relatedOrderName = account.getRelatedOrderName();
                if (StringUtils.isEmpty(relatedOrderName)) {
                    relatedOrderName = "——";
                }
                row.createCell(10).setCellValue(relatedOrderName);
                String relatedObject = account.getRelatedObject();
                if (StringUtils.isEmpty(relatedObject)) {
                    relatedObject = "——";
                }
                row.createCell(11).setCellValue(relatedObject);
                row.createCell(12).setCellValue(account.getAccountGroupName());
                String groupAbsoluteName = account.getGroupAbsoluteName();
                int width13 = (2 + groupAbsoluteName.length()) * 2 * 256;
                if (width13 > sheet.getColumnWidth(13)) {
                    sheet.setColumnWidth(13, width13);
                }
                row.createCell(13).setCellValue(groupAbsoluteName);
                for (int i = 0; i < 14; i++) {
                    Cell cell = row.getCell(i);
                    if (cell.getCellType() == XSSFCell.CELL_TYPE_NUMERIC) {
                        cell.setCellStyle(cellStyle2);
                    } else {
                        cell.setCellStyle(cellStyle1);
                    }
                }
            });
        }
        return workbook;
    }

    /**
     * 对account坐下处理，按照 type英文 分类
     *
     * @return {@code <type英文, Float>, <type英文, List<Account>>, <类型, 总费用>, <类型, List<Account>>>} 这里的总费用保留2位小数
     */
    private KeyValue<LinkedHashMap<String, Float>, LinkedHashMap<String, List<Account>>> accountByType(@NonNull List<Account> accounts) {
        LinkedHashMap<String, Float> typeTotal = CollectionUtil.mapKeyClone(getLocalResourceTypes(), v -> 0f);
        LinkedHashMap<String, List<Account>> typeAccounts = CollectionUtil.mapKeyClone(getLocalResourceTypes(), v -> null);
        CollectionUtil.foreach(accounts, account -> {
            String type = account.getResourceTypeName();
            typeTotal.merge(type, account.getCost(), Float::sum);
            List<Account> list = typeAccounts.computeIfAbsent(type, v -> new ArrayList<>());
            list.add(account);
        });
        return new KeyValue<>(typeTotal, typeAccounts);
    }

    /**
     * 按统一顺序返回统一类型的英文及中文 <br/>
     * 注意，这里的类型需要与AccountService.listTypeName()返回值一一对应。走这个方法目的是为了统一顺序
     *
     * @return {@code <英文, 中文>}，例如 {@code <server, 云主机>}
     */
    public LinkedHashMap<String, String> getLocalResourceTypes() {
        setLocalResourceTypes(getAllResourceTypes());
        return localResourceTypes;
    }

    private void setLocalResourceTypes(LinkedHashMap<String, String> allResourceTypes) {
        // 防止线程安全问题，这里new新的对象
        LinkedHashMap<String, String> resourceTypes = new LinkedHashMap<>();
        // 过滤
        ArrayList<String> types = listTypeName();
        for (Map.Entry<String, String> entry : allResourceTypes.entrySet()) {
            if (types.contains(entry.getKey())) {
                resourceTypes.put(entry.getKey(), entry.getValue());
            }
        }
        localResourceTypes = resourceTypes;
    }

    /**
     * 按统一顺序返回统一类型的英文及中文 <br/>
     *
     * @return {@code <英文, 中文>}，例如 {@code <server, 云主机>}
     */
    private LinkedHashMap<String, String> getAllResourceTypes() {
        LinkedHashMap<String, String> resourceTypes = new LinkedHashMap<>();
        List<Map<String, String>> mapList = listTypeEnAndName();
        for (Map<String, String> node : mapList) {
            resourceTypes.put(node.get("name"), node.get("value"));
        }
        return resourceTypes;
    }


    public List<Map<String, String>> listTypeEnAndName() {
        LambdaQueryWrapper<Type> query = Wrappers.lambdaQuery();
        List<Type> typeList = typeMapper.selectList(query.orderByAsc(Type::getSort));
        List<Map<String, String>> data = CollectionUtil.listToList(typeList, type -> {
            Map<String, String> map = new HashMap<>();
            map.put("name", type.getEnName());
            map.put("value", type.getName());
            return map;
        });
        return data;
    }


    /**
     * 获取账单页面展示内容-月类型趋势
     */
    @Override
    public AccountViewDTO getAccountMonthTypeView(int year, String accountGroupId) {
        // 判断是否有权限
//        this.checkViewAllowed(accountGroupId);
        // 先定义返回的数据的结构：view, viewDetail
        LinkedHashMap<String, String> types = getLocalResourceTypes();
        LinkedHashMap<String, float[]> view = CollectionUtil.mapValueAsKeyClone(types, v -> new float[12]);
        LinkedHashMap<String, LinkedHashMap<String, Object>[]> viewDetail = CollectionUtil.mapValueAsKeyClone(types, v -> {
            @SuppressWarnings("unchecked")
            LinkedHashMap<String, Object>[] maps = new LinkedHashMap[12];
            for (int i = 0; i < 12; i++) {
                maps[i] = new LinkedHashMap<>();
            }
            return maps;
        });
        // 查询账单数据
        if ("0".equals(accountGroupId)) {
            accountGroupId = null;
        }
        ArrayList<Map<String, Object>> monthTypeCostList = accountMapper.listMonthTypeCostByYearGroup(year, accountGroupId);
        ArrayList<LinkedHashMap<String, Object>> lastMonth12TypeCostResult = accountMapper.listTypeCostByYearMonthGroup(year - 1, 12, accountGroupId);
        Map<String, Float> lastMonth12TypeCost = CollectionUtil.listMapF2FToMap(
                lastMonth12TypeCostResult, v1 -> (String) v1, v2 -> ((Number) v2).floatValue()
        );
        float[] monthTotal = new float[12];
        CollectionUtil.foreach(monthTypeCostList, monthTypeCost -> {
            int month = ((Number) monthTypeCost.get("month")).intValue();
            float cost = ((Number) monthTypeCost.get("cost")).floatValue();
            monthTotal[month - 1] += cost;
        });
        // 将数据塞入：view, viewDetail
        int typeNum = view.size();
        CollectionUtil.foreach(monthTypeCostList, monthTypeCost -> {
            int month = ((Number) monthTypeCost.get("month")).intValue();
            float cost = ((Number) monthTypeCost.get("cost")).floatValue();
            // 账单为0没意义，就当做没数据，直接跳过。
            if (cost <= 0) {
                return;
            }
            String type = (String) monthTypeCost.get("type");
            String typeValue = types.get(type);
            // 计算view
            float[] fs = view.computeIfAbsent(typeValue, v -> new float[12]);
            fs[month - 1] += cost;
            // 计算viewDetail
            Map<String, Object> typeDetail = viewDetail.get(typeValue)[month - 1];
            typeDetail.put(typeValue, NumberUtil.floatScale(cost));
            if (monthTotal[month - 1] == 0f) {
                typeDetail.put("占比", NumberUtil.scalePercentStr(1 / typeNum));
            } else {
                typeDetail.put("占比", NumberUtil.scalePercentStr(cost / monthTotal[month - 1]));
            }
            int offset;
            float lastMonthCost;
            if (month == 1) {
                Float f = lastMonth12TypeCost.get(type);
                lastMonthCost = f == null ? 0f : f;
            } else {
                lastMonthCost = view.get(typeValue)[month - 2];
            }
            if (lastMonthCost == cost) {
                offset = 0;
            } else if (lastMonthCost == 0f) {
                offset = 100;
            } else {
                offset = NumberUtil.floatToInt((cost / lastMonthCost - 1) * 100);
            }
            typeDetail.put("同比上月", offset);
        });
        // 浮点数计算有误差，格式化数据，保留两位小数
        for (float[] fs : view.values()) {
            for (int i = 0; i < 12; i++) {
                fs[i] = NumberUtil.floatScale(fs[i]);
            }
        }
        AccountViewDTO accountViewDTO = new AccountViewDTO();
        accountViewDTO.setMonthTypeView(view);
        accountViewDTO.setMonthTypeViewDetail(viewDetail);
        return accountViewDTO;
    }

}
